package com.zang.blogz.service.impl;

import cn.dev33.satoken.stp.SaLoginConfig;
import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.zang.blogz.constant.ResultConstant;
import com.zang.blogz.constant.SecurityConstant;
import com.zang.blogz.dto.SocialTokenDTO;
import com.zang.blogz.dto.SocialUserInfoDTO;
import com.zang.blogz.enmus.LoginTypeEnum;
import com.zang.blogz.entity.UserAuth;
import com.zang.blogz.entity.UserInfo;
import com.zang.blogz.entity.UserRole;
import com.zang.blogz.mail.VerificationCodeInfo;
import com.zang.blogz.mapper.UserAuthMapper;
import com.zang.blogz.model.input.ro.AdminRegisterRO;
import com.zang.blogz.model.input.ro.QQLoginRO;
import com.zang.blogz.model.output.UserInfoVO;
import com.zang.blogz.result.Result;
import com.zang.blogz.security.Sm4Util;
import com.zang.blogz.service.UserAuthService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zang.blogz.service.UserInfoService;
import com.zang.blogz.service.UserRoleService;
import io.github.vampireachao.stream.core.optional.Opp;
import com.zang.blogz.strategy.context.SocialLoginStrategyContext;
import com.zang.blogz.utils.IpUtil;
import io.github.vampireachao.stream.plugin.mybatisplus.One;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.List;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author 朵橙i
 * @since 2022-06-17
 */
@Service
public class UserAuthServiceImpl extends ServiceImpl<UserAuthMapper, UserAuth> implements UserAuthService {

    private static final String VERIFICATION_CODE = "VerificationCode:";


    @Value("${userinfo.default.avatar}")
    private String defaultAvatar;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    @Lazy
    private UserAuthService userAuthService;

    @Autowired
    private UserInfoService userInfoService;

    @Autowired
    private UserRoleService userRoleService;

    @Autowired
    private SocialLoginStrategyContext socialLoginStrategyContext;

    @Resource
    private HttpServletRequest request;

    /**
     * 登录
     *
     */
    @Override
    @Transactional
    public Result<UserInfoVO> login(String username, String pwd) throws Exception {

        QueryWrapper<UserAuth> userAuthQueryWrapper = new QueryWrapper<>();
        userAuthQueryWrapper.lambda().eq(UserAuth::getUsername, username);

        UserAuth userAuth = this.getOne(userAuthQueryWrapper);
        if (userAuth == null) {

            return Result.error(ResultConstant.LoginMessage.USER_NOT_EXIST);
        }

        if (StringUtils.isBlank(userAuth.getSecretKey())){

            throw new RuntimeException(ResultConstant.LoginMessage.S_KEY_NOT_EXIST);
        }

        if (!StringUtils.equals(Sm4Util.uncoverMsg(userAuth.getSecretKey(), userAuth.getPassword()), pwd)) {

            return Result.error(ResultConstant.LoginMessage.PASSWORD_INCORRECT);
        }

        // 获取用户登录ip
        String ipAddress = IpUtil.getIpAddress(request);
        String ipSource = IpUtil.getIpSource(ipAddress);

        StpUtil.login(userAuth.getUserInfoId(), SaLoginConfig
                .setExtra("name", userAuth.getUsername())
                .setExtra("id", userAuth.getUserInfoId())
                .setExtra("ipAddress", ipAddress)
                .setExtra("ipSource", ipSource)

        );

        UserInfo userInfo = One.of(UserInfo::getId).eq(userAuth.getUserInfoId()).query();

        return Result.ok(ResultConstant.LoginMessage.LOGIN_SUCCESS,
                UserInfoVO.builder()
                .token(StpUtil.getTokenValue())
                .webSite(userInfo.getWebSite())
                .ipSource(userAuth.getIpSource())
                .ipSource(userAuth.getIpSource())
                .nickName(userInfo.getNickname())
                .email(userInfo.getEmail())
                .avatar(userInfo.getAvatar()).build());

    }

    /**
     * 退出登录
     *
     * @return
     */
    @Override
    public Result<String> out() {
        
        StpUtil.logout();
        return Result.ok(ResultConstant.LogoutMessage.LOGOUT_SUCCESS);
    }

    /**
     * 获得注册验证码
     *
     * @param email
     */
    @Override
    public Result<String> getVerificationCode(String email) {

        if (Opp.ofStr(email).isEmpty()) {

            return Result.error(ResultConstant.RegisterMessage.MAIL_NULL);
        }

        List<UserInfo> userInfos = userInfoService.list(new LambdaQueryWrapper<UserInfo>().eq(UserInfo::getEmail, email));

        if (userInfos.size() > 1) {

            return Result.error(ResultConstant.RegisterMessage.MAIL_EXIST);
        }


        if (redisTemplate.opsForHash().hasKey(VERIFICATION_CODE, email + ":")) {

            return Result.error(ResultConstant.RegisterMessage.VERIFICATION_CODE_NO_EXIST);
        }
        //发送验证码
        String value = String.valueOf(RandomUtil.randomInt(100000, 999999));

        //得到当前时间
        Date date = DateUtil.date(System.currentTimeMillis());
        //过期时间
        Date expiredDate = DateUtil.offset(date, DateField.MINUTE, 3);
        String dateTime = DateUtil.formatDateTime(expiredDate);
        String smallValue = value + "_" + dateTime;

        redisTemplate.opsForHash().put(VERIFICATION_CODE, email + ":", smallValue);

        VerificationCodeInfo verificationCodeInfo = VerificationCodeInfo.builder()

                .verificationCode(value)
                .email(email).build();

        sendEmail(verificationCodeInfo);

        return Result.ok(ResultConstant.RegisterMessage.VERIFICATION_CODE_SUCCESS);
    }

    /**
     * 用户注册
     *
     * @param adminRegisterRo
     * @return
     */
    @Transactional
    @Override
    public Result<String> adminRegister(AdminRegisterRO adminRegisterRo) throws Exception {

        // 判断验证码是否为空
        if (Opp.ofStr(adminRegisterRo.getVerificationCode()).isEmpty()) {

            return Result.error(ResultConstant.RegisterMessage.VERIFICATION_CODE_NULL);
        }

        String register = (String) redisTemplate.opsForHash().get(VERIFICATION_CODE, adminRegisterRo.getUserName() + ":");
        String[] s = register.split("_");
        register = s[0];

        String expired = s[1];
        DateTime expiredDate = DateUtil.parse(expired, "yyyy-MM-dd HH:mm");
        Date date = DateUtil.date(System.currentTimeMillis());
        // 判断验证码是否过期
        if (date.after(expiredDate)) {

            redisTemplate.opsForHash().delete(VERIFICATION_CODE, adminRegisterRo.getUserName() + ":");
            return Result.error(ResultConstant.RegisterMessage.VERIFICATION_CODE_EXIST);
        }

        // 判断验证码是否正确
        if (!StringUtils.equals(adminRegisterRo.getVerificationCode(), register)) {

            return Result.error(ResultConstant.RegisterMessage.VERIFICATION_CODE_INCORRECT);
        }
        // 动态秘钥
        String generateKeyString = Sm4Util.generateKeyString();
        adminRegisterRo.setPassWord(Sm4Util.protectMsg(generateKeyString, adminRegisterRo.getPassWord()));

        boolean saveUserInfo = userInfoService.save(UserInfo.builder()

                .avatar(defaultAvatar)
                .email(adminRegisterRo.getUserName())
                .nickname(SecurityConstant.USER + adminRegisterRo.getUserName())
                .build());

        UserInfo one = userInfoService.getOne(new LambdaQueryWrapper<UserInfo>().eq(UserInfo::getEmail, adminRegisterRo.getUserName()));

        boolean saveUserAuth = userAuthService.save(UserAuth.builder()
                .username(adminRegisterRo.getUserName())
                .password(adminRegisterRo.getPassWord())
                .secretKey(generateKeyString)
                .userInfoId(one.getId()).build());
        // 设置默认角色
        userRoleService.save(UserRole.builder().userId(one.getId()).roleId(2).build());

        redisTemplate.opsForHash().delete(VERIFICATION_CODE, adminRegisterRo.getUserName() + ":");

        if (saveUserAuth && saveUserInfo) {

            return Result.ok(ResultConstant.RegisterMessage.REGISTER_SUCCESS);
        }

        return Result.error(ResultConstant.RegisterMessage.REGISTER_FAIL);
    }

    @Override
    public UserAuth getUserAuth(LoginTypeEnum loginTypeEnum, SocialUserInfoDTO socialTokenDTO) {

        LambdaQueryWrapper<UserAuth> queryWrapper = new LambdaQueryWrapper<>();

        queryWrapper.eq(UserAuth::getUsername, socialTokenDTO.getNickname());
        queryWrapper.eq(UserAuth::getLoginType, loginTypeEnum.getType());

        return getOne(queryWrapper);
    }

    @Override
    public Result<UserInfoVO> qqLogin(QQLoginRO qqLoginRO) {

        return socialLoginStrategyContext.executeLoginStrategy(JSON.toJSONString(qqLoginRO), LoginTypeEnum.QQ);
    }

    @Override
    public Result<UserInfoVO> giteeLogin(String code) {

        return socialLoginStrategyContext.executeLoginStrategy(code, LoginTypeEnum.GITEE);
    }

    /**
     * 异步发送邮件通知
     *
     * @param verificationCodeInfo 发送验证码所需要的信息
     */
    @Async
    public void sendEmail(VerificationCodeInfo verificationCodeInfo) {

        rabbitTemplate.convertAndSend("verificationCode-queue", verificationCodeInfo);

    }
}
